iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Software Development

Oops! OOPP: An Introduction to Object-Oriented Programming in Python系列 第 20

子類別存取父類別的私有屬性和方法,真的不行嗎?

  • 分享至 

  • xImage
  •  

今天沒有新進度,只是寫code印證一下之前的「理論」。


  • 還記得筆者以前說過,類別的私有(private)屬性和方法,只能在類別自身內存取,連它的子子孫孫(繼承者)都存取不到嗎?
  • 現在既然已進入繼承階段,今天就讓我們詳細驗證一下,看這個講法究竟是否屬實。

驗證私有屬性

  • 先定義父類別:

    class Tree():
        _count = 0    # protected     
    
        def __init__(self, breed: str, age: int, height: int):   # constructor
            self.breed = breed          # public
            self._age = age             # protected
            self.__height = height      # private
            Tree._count += 1
    
        def show_info(self):
            print(f'{self.breed=:10}{self._age=:<10,}{self.__height=:<10,}')
    
        @classmethod
        def show_count(cls):
            print(f'{cls._count=}')
    
  • 上面這段code,有公開層級的breed,保護層級的_age和私有層級的__height三個實例屬性,還有一個「保護」(註1)層級的類別屬性__count

  • 子類別設一個就好。其中show_hardwood_count(cls)的方法,加不加@classmethod裝飾器,似乎不會影響結果:

    class Hardwood(Tree):   # derived from Tree()
        # 無自己的constructor。
    
        # @classmethod   # 不管加不加這個decorator都沒問題。
        def show_hardwood_count(cls):
            print(f'{cls._count = :<10}')
    
  • 試從子類別取父類別的類別屬性_count

    try:
        hardwood = Hardwood('cedar', 1_000, 57)
        hardwood.show_hardwood_count()
    except Exception as e:
        print(str(e)) 
    
  • 結果順利取到資料:
    https://ithelp.ithome.com.tw/upload/images/20221005/201484852DO6WB6KaP.png

  • 這是當然的,因為類別屬性_count只是保護層級。就算在C-like這掛真正有保護層級的物件導向語言,保護級的屬性,子類別都是存取得到的。況且Python的保護層級,只是「約定俗成」,語法上的效果就和公開層級無異。

  • 父類別重新設計,將_count改為私有層級的__count

    class Tree():
        __count = 0     # private    
    
        def __init__(self, breed: str, age: int, height: int):   # constructor
            self.breed = breed       # public
            self._age = age          # protected
            self.__height = height   # private
            Tree.__count += 1
    
        def show_info(self):
            print(f'{self.breed=:10}{self._age=:<10,}{self.__height=:<10,}')
    
        @classmethod
        def show_count(cls):
            print(f'{cls.__count=}')
    
  • 子類別設計隨著略加修改:

    class Hardwood(Tree):   # 繼承自Tree。
        @classmethod
        def show_hardwood_count(cls):
            print(f'{cls.__count = :<10}')  # 只改這行。
    
  • 測試程式倒不必改,不過還是再貼一次比較清楚:

    try:
        hardwood = Hardwood('cedar', 1_000, 57)
        hardwood.show_hardwood_count()
    except Exception as e:
        print(str(e)) 
    
  • 結果拋出異常:
    https://ithelp.ithome.com.tw/upload/images/20221005/20148485eWoQPFhwbg.png

  • 以上證明子類別無法存取父類別中私有層級的類別屬性

  • 再看實例屬性。父類別的設計和上面完全相同。為方便讀者閱讀,還是再貼一次:

    class Tree():
        __count = 0     # private    
    
        def __init__(self, breed: str, age: int, height: int):   # constructor
            self.breed = breed       # public
            self._age = age          # protected
            self.__height = height   # private
            Tree.__count += 1
    
        def show_info(self):
            print(f'{self.breed=:10}{self._age=:<10,}{self.__height=:<10,}')
    
        @classmethod
        def show_count(cls):
            print(f'{cls.__count=}')
    
  • 子類別則改為這樣:

    class Hardwood(Tree):   # 繼承自Tree。
        def show_hardwood_info(self):
            print(f'{self.breed=:10}{self._age=:<10,}')
            print(f'{self.__height=}')
    
  • 測試程式:建立一個Hardwood型別的物件,看能否在子類別內部存取父類別的私有層級實例屬性

    try:
        hardwood = Hardwood('cedar', 1_000, 57)
        hardwood.show_hardwood_info()
    except Exception as e:
        print(str(e)) 
    
  • 結果依然拋出異常:
    https://ithelp.ithome.com.tw/upload/images/20221005/20148485NWrgRr7GFy.png

  • 小結:本日的「小哉問」子類別存取父類別的Private Attributes,真的不行嗎?,答案是:Yes, 真的不行

    • 通過以上的反覆驗證,確定只要是私有(private)屬性,不管是類別屬性(class attributes),還是實例屬性(instance attributes),其子孫輩都無法存取。
    • 要存取,唯有透過property(這是既方便又安全的存取方式)。昨天的code已展示透過property存取私有屬性,本篇不再重複。

驗證私有方法

  • 接下來要驗證子類別是否能呼叫父類別的私有方法:

    class Tree():
    
        def __init__(self, breed: str, age: int, height: int):   # constructor
            self.breed = breed
            self._age = age
            self.__height = height
    
        def show_info(self):     # public method
            print(f'{self.breed=:10}{self._age=:<10,}{self.__height=:<10,}')
    
        def _show_info(self):    # protected method
            print(f'{self.breed=:10}{self._age=:<10,}{self.__height=:<10,}')
    
        def __show_info(self):   # private method
            print(f'{self.breed=:10}{self._age=:<10,}{self.__height=:<10,}')
    
    
    class Hardwood(Tree):   # Inherited from Tree
        ...
    
  • 測試程式:

    try:
        hardwood = Hardwood('cedar', 3_560, 61)
        hardwood.show_info()     # fine
        hardwood._show_info()    # fine
        hardwood.__show_info()   # 這行會觸發exception。
    except Exception as e:
        print(str(e))   
    
  • 結果是:前兩行呼叫父類別hardwood.show_info()(公開)、hardwood._show_info()(保護)的方法都順利執行,而在呼叫父類別的私有方法hardwood.__show_info()時,踢到了一塊大鐵板。
    https://ithelp.ithome.com.tw/upload/images/20221005/20148485tzwhEpQeJM.png

  • 證明私有方法也不會傳給子孫。

結論

  • 無論是屬性或方法,只要是私有層級,就只有類別內部才能存取
  • 兢兢業業、克紹箕裘的乖兒郎,雖繼承父業,光宗耀祖,老爺子的私房錢卻一文也別想拿到。

註1: 別忘了筆者一再強調的:Python類別中保護層級的屬性和方法,只是大家約定的「默契」,並無實質「公權力」。


上一篇
OO第二大支柱Inheritance:起手式
下一篇
Overriding
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言